#include <stx/imp/cmn.hpp>

inline namespace stx
{
bool ProgramCmdLines::empty() const
{
    return this->opts_with_arg.empty() && this->opts_with_opt_arg.empty() &&
           this->opts_with_no_arg.empty() && this->program_args.empty();
}

ProgramArgParser&
ProgramArgParser::addShortOptions(std::string_view const sv_short_opts)
{
    if (sv_short_opts.empty())
    {
        RUNTIME_ERROR("short_options are empty."sv);
    }

    auto const n = sv_short_opts.size();
    for (auto i = 0uz; i < n;)
    {
        auto const c1 = sv_short_opts[i];
        if (!std::isalnum(c1))
        {
            RUNTIME_ERROR(sv_short_opts);
        }

        if (i < n - 1)
        {
            auto const c2 = sv_short_opts[i + 1];
            if (std::isalnum(c2))
            {
                insert(this->names_of_opt_with_no_arg_, std::string(1, c1));
                ++i;
                continue;
            }

            if (':' == c2)
            {
                insert(this->names_of_opt_with_arg_, std::string(1, c1));
            }
            else if (';' == c2)
            {
                insert(this->names_of_opt_with_opt_arg_, std::string(1, c1));
            }
            else
            {
                RUNTIME_ERROR(sv_short_opts);
            }

            i += 2;
            continue;
        }

        ASSERT(n - 1 == i);
        insert(this->names_of_opt_with_no_arg_, std::string(1, c1));
        break;
    }

    return *this;
}

ProgramArgParser& ProgramArgParser::addLongOptionWithRequiredArg(
    std::string_view const sv_opt_name)
{
    if (sv_opt_name.empty())
    {
        RUNTIME_ERROR("long_option_name cannot be empty."sv);
    }

    insert(this->names_of_opt_with_arg_, sv_opt_name.substr());
    return *this;
}

ProgramArgParser& ProgramArgParser::addLongOptionWithOptionalArg(
    std::string_view const sv_opt_name)
{
    if (sv_opt_name.empty())
    {
        RUNTIME_ERROR("long_option_name cannot be empty."sv);
    }

    insert(this->names_of_opt_with_opt_arg_, sv_opt_name.substr());
    return *this;
}

ProgramArgParser&
ProgramArgParser::addLongOptionWithNoArg(std::string_view const sv_opt_name)
{
    if (sv_opt_name.empty())
    {
        RUNTIME_ERROR("long_option_name cannot be empty."sv);
    }

    insert(this->names_of_opt_with_no_arg_, sv_opt_name.substr());
    return *this;
}

ProgramCmdLines ProgramArgParser::parse(int const         argc,
                                        char const* const argv[])
{
    this->checkDuplicateOptionNames_();

    auto ret = ProgramCmdLines{};

    for (auto i = 1; i < argc;)
    {
        auto const sv = tosv(argv[i]);

        auto option_name = std::string{};
        auto option_arg  = std::string{};

        auto const fn_handle_equal = [&, this]
        {
            if (this->names_of_opt_with_arg_.contains(option_name))
            {
                ret.opts_with_arg[std::move(option_name)] =
                    std::move(option_arg);
                ++i;
                return true;
            }

            if (this->names_of_opt_with_opt_arg_.contains(option_name))
            {
                ret.opts_with_opt_arg[std::move(option_name)] =
                    std::move(option_arg);
                ++i;
                return true;
            }

            return false;
        };

        auto const fn_handle_no_equal = [&, this]
        {
            if (this->names_of_opt_with_arg_.contains(option_name))
            {
                if (i + 1 >= argc)
                {
                    RUNTIME_ERROR("Required option '"sv,
                                  option_name,
                                  "' has no argument. command line: "sv,
                                  scat(" "_I, MemView(argv, argc)));
                }

                ret.opts_with_arg[std::move(option_name)] = argv[i + 1];
                i += 2;
                return true;
            }

            if (this->names_of_opt_with_opt_arg_.contains(option_name))
            {
                if (i + 1 >= argc)
                {
                    RUNTIME_ERROR("Required option '"sv,
                                  option_name,
                                  "' has no argument. command line: "sv,
                                  scat(" "_I, MemView(argv, argc)));
                }

                ret.opts_with_opt_arg[std::move(option_name)] = ""s;
                ++i;
                return true;
            }

            if (this->names_of_opt_with_no_arg_.contains(option_name))
            {
                insert(ret.opts_with_no_arg, std::move(option_name));
                ++i;
                return true;
            }

            return false;
        };

        if (isMatch(sv, rgx<R"(^-[a-zA-Z0-9]=\S+$)">()))
        {
            option_name = std::string(1, sv[1]);
            option_arg  = sv.substr(3);
            if (fn_handle_equal())
            {
                continue;
            }
        }
        else if (isMatch(sv, rgx<R"(^--[a-zA-Z0-9_-]{2,}=\S+$)">()))
        {
            auto const equal_sign_pos = sv.find('=');
            option_name =
                std::string(sv.begin() + 2, sv.begin() + equal_sign_pos);
            option_arg = sv.substr(equal_sign_pos + 1);
            if (fn_handle_equal())
            {
                continue;
            }
        }
        else if (isMatch(sv, rgx<R"(^-[a-zA-Z0-9]$)">()))
        {
            option_name = std::string(1, sv[1]);
            if (fn_handle_no_equal())
            {
                continue;
            }
        }
        else if (isMatch(sv, rgx<R"(^--[a-zA-Z0-9_-]{2,}$)">()))
        {
            option_name = sv.substr(2);
            if (fn_handle_no_equal())
            {
                continue;
            }
        }

        insert(ret.program_args, sv.substr());
        ++i;
    }

    return ret;
}

void ProgramArgParser::checkDuplicateOptionNames_() const
{
    static constinit auto sv_err_msg_prefix = "Duplicate option names: "sv;

    auto tmp = Set<ProgramOptionName>{};
    ranges::set_intersection(this->names_of_opt_with_arg_,
                             this->names_of_opt_with_opt_arg_,
                             ranges::inserter(tmp, tmp.begin()));
    if (!tmp.empty())
    {
        RUNTIME_ERROR(sv_err_msg_prefix,
                      scat("["_prefix, "|"_I, "]"_postfix, tmp));
    }

    ranges::set_intersection(this->names_of_opt_with_arg_,
                             this->names_of_opt_with_no_arg_,
                             ranges::inserter(tmp, tmp.begin()));
    if (!tmp.empty())
    {
        RUNTIME_ERROR(sv_err_msg_prefix,
                      scat("["_prefix, "|"_I, "]"_postfix, tmp));
    }

    ranges::set_intersection(this->names_of_opt_with_opt_arg_,
                             this->names_of_opt_with_no_arg_,
                             ranges::inserter(tmp, tmp.begin()));
    if (!tmp.empty())
    {
        RUNTIME_ERROR(sv_err_msg_prefix,
                      scat("["_prefix, "|"_I, "]"_postfix, tmp));
    }
}
} // namespace stx